﻿#region Using directives

using System;
using System.IO;
using System.Xml;
using System.Text;
using System.CodeDom;
using System.Collections.Generic;

using Weazel.Gezel.Model;

#endregion

namespace Weazel.Gezel.Conversions.Model
{
  public abstract class SignalTypeBase : Type
  {
    protected ISignalEntity entity;
    protected ControllerType controllerType;

    protected IntegerField valueField;
    protected Field cycleField;
    protected Field entityNameField;

    protected Method evaluateMethod;
    protected Method initializeMethod;

    private readonly Dictionary<int, List<SignalType>> signalDependencies;
    public Dictionary<int, List<SignalType>> SignalDependencies
    {
      get { return signalDependencies; }
    }

    private readonly Dictionary<int, List<OutPortType>> outportDependencies;
    public Dictionary<int, List<OutPortType>> OutportDependencies
    {
      get { return outportDependencies; }
    }

    private readonly Dictionary<int, List<RegisterType>> registerDependencies;
    public Dictionary<int, List<RegisterType>> RegisterDependencies
    {
      get { return registerDependencies; }
    }

    public SignalTypeBase(string name, ISignalEntity entity, ControllerType controllerType, SoftwareModel softwareModel, int width, bool signed) : 
      base(name, softwareModel)
    {
      this.entity = entity;
      this.controllerType = controllerType;

      this.registerDependencies = new Dictionary<int, List<RegisterType>>();
      this.outportDependencies = new Dictionary<int, List<OutPortType>>();
      this.signalDependencies = new Dictionary<int, List<SignalType>>();

      //this.comments["definers"] = DefinersToString();

      valueField = AddIntegerField("Value", width, signed, "0");
      entityNameField = AddField("EntityName", "String");

      cycleField = AddField("Cycle", "Int32");
      cycleField.Comments.Add(new CodeCommentStatement("The cycle for which the value field was last evaluated (debug purpose)", true));
			
			// ports on ipblocks does not have controllers
      if (controllerType != null)
      {
        this.comments["controlled-by"] = controllerType.Controller.Name;

        buildEvaluateMethod();

        buildInitializerMethod();
      }
      else
      {
        // add empty evaluate method to satisfy interface 'IOutport'
        evaluateMethod = AddMethod("Evaluate");
        evaluateMethod.Statements.Add(new CodeCommentStatement("dummy method"));
      }      
    }

    private string printDependencies()
    {
      StringBuilder builder = new StringBuilder();

      foreach (ExecutionSet sfgSet in controllerType.ExecutionSets)
      {
        builder.AppendFormat("control step {0} (", sfgSet.ControlStepId);

        foreach (SignalFlowGraph sfg in sfgSet)
        {
          builder.AppendFormat("{0},", sfg.LocalName);
        }

        builder.Remove(builder.Length - 1, 1);
				builder.Append(")");

				if (registerDependencies[sfgSet.ControlStepId].Count > 0)
				{
					builder.Append("\n\tregister dependencies: ");

					foreach (RegisterType reg in registerDependencies[sfgSet.ControlStepId])
					{
						builder.AppendFormat("{0},", reg.Name);
					}
					builder.Remove(builder.Length - 1, 1);
				}

				if (outportDependencies[sfgSet.ControlStepId].Count > 0)
				{
					builder.Append("\n\toutport dependencies: ");

					foreach (OutPortType reg in outportDependencies[sfgSet.ControlStepId])
					{
						builder.AppendFormat("{0},", reg.Name);
					}
					builder.Remove(builder.Length - 1, 1);
				}

				if (signalDependencies[sfgSet.ControlStepId].Count > 0)
				{
					builder.Append("\n\tsignal dependencies: ");

					foreach (SignalType reg in signalDependencies[sfgSet.ControlStepId])
					{
						builder.AppendFormat("{0},", reg.Name);
					}
					builder.Remove(builder.Length - 1, 1);
				}

        builder.AppendLine("");
      }

      return builder.ToString();
    }

    public void AnalyzeDependencies()
    {
      if (controllerType != null)
      {
        foreach (ExecutionSet sfgSet in controllerType.ExecutionSets)
        {
          signalDependencies[sfgSet.ControlStepId] = new List<SignalType>();
          outportDependencies[sfgSet.ControlStepId] = new List<OutPortType>();
          registerDependencies[sfgSet.ControlStepId] = new List<RegisterType>();
        }

        foreach (Expression expr in entity.Definers)
        {          
          SignalFlowGraph sfg = entity.DefiningSignalFlowGraphs[expr];
          int[] controlSteps = controllerType.GetControlStepsForSfg(sfg);

          List<LeafExpression> leaves = new List<LeafExpression>();
          expr.GetDependentLeafExpressions(ref leaves);

          foreach (LeafExpression leaf in leaves)
          {
            foreach (int controlStep in controlSteps)
            {
              if (leaf.ReferencedEntity is Signal)
              {
                SignalType signalType = softwareModel.GetTypeForSignal(leaf.ReferencedEntity as Signal);
                if (!signalDependencies[controlStep].Contains(signalType))
                  signalDependencies[controlStep].Add(signalType);
              }
              else if (leaf.ReferencedEntity is OutPort)
              {
                OutPortType outportType = softwareModel.GetTypeForOutport(leaf.ReferencedEntity as OutPort);
                if (!outportDependencies[controlStep].Contains(outportType))
                  outportDependencies[controlStep].Add(outportType);
              }
              else if (leaf.ReferencedEntity is Register)
              {
                RegisterType registerType = softwareModel.GetTypeForRegister(leaf.ReferencedEntity as Register);
                if (!registerDependencies[controlStep].Contains(registerType))
                  registerDependencies[controlStep].Add(registerType);
              }
              else if (leaf.ReferencedEntity is InPort)
              {
                InPort inport = leaf.ReferencedEntity as InPort;

                if (softwareModel.PortMappings.ContainsInport(inport))
                {
                  OutPort outPort = softwareModel.PortMappings[leaf.ReferencedEntity as InPort];
                  OutPortType outportType = softwareModel.GetTypeForOutport(outPort);
                  if (!outportDependencies[controlStep].Contains(outportType))
                    outportDependencies[controlStep].Add(outportType);
                }
                else
                {
                  System.Diagnostics.Trace.Assert(softwareModel.SignalMappings.ContainsInport(inport));

                  Signal signal = softwareModel.SignalMappings[leaf.ReferencedEntity as InPort];
                  SignalType signalType = softwareModel.GetTypeForSignal(signal);
                  if (!signalDependencies[controlStep].Contains(signalType))
                    signalDependencies[controlStep].Add(signalType);
                }
              }
            }
          }
        }
        comments["dependencies"] = printDependencies();
      }
    }

    protected string DefinersToString()
    {
      XmlDocument doc = new XmlDocument();
      XmlElement root = doc.CreateElement("definerlist");
      doc.AppendChild(root);

      StringBuilder builder = new StringBuilder();

      foreach (Expression expr in entity.Definers)
      {
        SignalFlowGraph sfg = entity.DefiningSignalFlowGraphs[expr];
        int[] controlSteps = controllerType.GetControlStepsForSfg(sfg);

        XmlElement elem = doc.CreateElement("definer");

        XmlAttribute sfgAttr = doc.CreateAttribute("sfg");
        sfgAttr.Value = sfg.Name;
        elem.Attributes.Append(sfgAttr);

        XmlAttribute controlStepsAttribute = doc.CreateAttribute("controlSteps");
        controlStepsAttribute.Value = arrayToString(controlSteps);
        elem.Attributes.Append(controlStepsAttribute);


        elem.InnerText = string.Format("{0} = {1}", entity.LocalName, expr.ToString());
        root.AppendChild(elem);
      }

      //return builder.ToString();
      StringWriter writer = new StringWriter();
      XmlTextWriter xmlWriter = new XmlTextWriter(writer);
      xmlWriter.Formatting = Formatting.Indented;
      xmlWriter.Indentation = 2;
      root.WriteContentTo(xmlWriter);
      return writer.ToString();
    }

    private string arrayToString(int[] array)
    {
			if (array.Length == 0)
				return string.Empty;

			StringBuilder builder = new StringBuilder();
      foreach (int i in array)
        builder.AppendFormat("{0}, ", i);

      return builder.ToString().Substring(0, builder.Length - 2);
    }

    protected void buildInitializerMethod()
    {
			if(initializeMethod == null)
				initializeMethod = AddMethod("Initialize");
      
			initializeMethod.Comments["summary"] = string.Format("Initializes the <see cref=\"{0}\"/> instance.", Name);

      //// init Cycle field to zero
      //initializeMethod.Statements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "Cycle"), new CodePrimitiveExpression(0)));

      // initialize integer fields
      foreach (Field field in this.Fields.Values)
      {
        IntegerField integerField = field as IntegerField;
        if (integerField != null)
        {
          if (integerField.InitialValue != "0")
          {
						initializeMethod.Statements.Add(
							new CodeAssignStatement(
								new CodeFieldReferenceExpression(
									new CodeThisReferenceExpression(), 
									integerField.Name
								),
								new CodeObjectCreateExpression(
									"Integer",
									new CodePrimitiveExpression(integerField.Width),
									new CodePrimitiveExpression(integerField.InitialValue),
									new CodePrimitiveExpression(integerField.Signed ? true : false)
								)
							)
						);
          }
          else
          {
						initializeMethod.Statements.Add(
							new CodeAssignStatement(
								new CodeFieldReferenceExpression(
									new CodeThisReferenceExpression(),
									integerField.Name
								),
								new CodeObjectCreateExpression(
									"Integer",
									new CodePrimitiveExpression(integerField.Width),
									new CodePrimitiveExpression(integerField.Signed ? true : false)
								)
							)
						);
          }
        }
      }
    }

    private void buildEvaluateMethod()
    {
      evaluateMethod = AddMethod("Evaluate");

      // this is a signal which is connected to an outport of a child datapath or ipblock
      if (this is SignalType && ((this as SignalType).entity as Signal).OutportDriver != null)
      {
				evaluateMethod.Statements.Add(new CodeCommentStatement("Signal is connected to nested outport"));
				
				OutPort driver = ((this as SignalType).entity as Signal).OutportDriver;
        string typeName = string.Format("outPort_{0}", driver.Name.Replace(".", "_"));
        CodeFieldReferenceExpression fieldReference =
            new CodeFieldReferenceExpression(
              new CodeFieldReferenceExpression(
                new CodeTypeReferenceExpression("Simulator"),
                typeName
              ),
              "Value"
            );
        CodeMethodReferenceExpression method = new CodeMethodReferenceExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "Value"), "ValueCopy");
        evaluateMethod.Statements.Add(new CodeMethodInvokeExpression(method, fieldReference));
        return;
      }

			// this is a outport which is connected to an outport of a child datapath or ipblock
			if (this is OutPortType && ((this as OutPortType).entity as OutPort).OutportDriver != null)
      {

				evaluateMethod.Statements.Add(new CodeCommentStatement("Outport is connected to nested outport"));
				evaluateMethod.Statements.Add(new CodeCommentStatement("Todo: generate code"));

        throw new NotImplementedException();
      }

			CodeSnippetStatement switchStatement = null;
			switch (softwareModel.Language)
			{
				case TargetLanguage.CSharp:
					switchStatement = new CodeSnippetStatement(string.Format("      switch(Simulator.controller_{0}.CurrentControlStep)", controllerType.Controller.Name.Replace(".", "_")));
					break;
				case TargetLanguage.VisualC:
				case TargetLanguage.IsoCpp:
					switchStatement = new CodeSnippetStatement(string.Format("      switch(Simulator::controller_{0}->CurrentControlStep)", controllerType.Controller.Name.Replace(".", "_")));
					break;
				case TargetLanguage.VisualBasic:
				case TargetLanguage.JSharp:
				default:
					throw new ApplicationException("Unsupported language");
			}

			System.Diagnostics.Debug.Assert(switchStatement != null);
      evaluateMethod.Statements.Add(switchStatement);
			evaluateMethod.Statements.Add(new CodeSnippetStatement("      {"));

      foreach (ExecutionSet sfgSet in controllerType.ExecutionSets)
      {
        Expression definingExpression = null;
        foreach (Expression expr in entity.Definers)
        {
          if (sfgSet.Contains(entity.DefiningSignalFlowGraphs[expr]))
          {
            definingExpression = expr;
            break;
          }
        }

        if (definingExpression != null)
        {
          evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("      case {0}:", sfgSet.ControlStepId)));
          CodeFieldReferenceExpression rhsIdentifier = ExpressionConversionHelper.BuildEvaluateMethodRecursive(definingExpression, evaluateMethod.Statements, this);
					CodeMethodReferenceExpression method = new CodeMethodReferenceExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "Value"), "ValueCopy");
					
					evaluateMethod.Statements.Add(new CodeMethodInvokeExpression(method, rhsIdentifier));
					evaluateMethod.Statements.Add(new CodeSnippetStatement("        break;"));
        }
        else 
          System.Diagnostics.Debug.Assert(this is RegisterType || this is SignalType, this.GetType().Name);
      }

      if (this is OutPortType)
      {
				evaluateMethod.Statements.Add(new CodeSnippetStatement("      default:"));
				CodePrimitiveExpression messageExpression = new CodePrimitiveExpression("Internal Error - no definer for outport");
				CodeObjectCreateExpression createExpression = new CodeObjectCreateExpression("ApplicationException", messageExpression);
				evaluateMethod.Statements.Add(new CodeThrowExceptionStatement(createExpression));
      }
      // add closing bracket of switch statement
      evaluateMethod.Statements.Add(new CodeSnippetStatement("      }"));
    }
  }
}
